10장. 세 힘을 함께 맞추기

원문 10장 "결합 균형"을 입문자용으로 다시 풀었다.
코드는 분위기만 보면 된다. 중요한 것은 "세기, 거리, 변동성을 따로 보지 말고 같이 본다"는 감각이다.

이전 장까지 우리는 결합을 세 방향에서 봤다.

  1. 얼마나 세게 묶였나: 결합 세기
  2. 얼마나 멀리 떨어졌나: 결합 거리
  3. 얼마나 자주 바뀌나: 변동성

10장의 핵심은 간단하다.

좋은 설계는 결합을 0으로 만드는 것이 아니라, 세 힘의 균형을 맞추는 것이다.

어떤 연결은 가까워야 한다.
어떤 연결은 약해야 한다.
어떤 연결은 자주 바뀌지 않으니 강해도 괜찮다.
반대로 자주 바뀌는 것끼리는 조금만 강하게 묶여도 금방 피곤해진다.

이제부터는 한 숫자만 보지 않는다.


0. 이 장의 새 단어

단어 아주 쉬운 뜻
균형 한쪽만 좋게 만들지 않고 전체 비용이 낮게 맞춰진 상태
결합 균형 세기, 거리, 변동성이 서로 어울리는 상태
유지보수 노력 변경할 때 드는 실제 수고
불균형 어느 한 차원이 다른 차원과 맞지 않아 변경 비용이 커지는 상태
국소 비용 바로 옆 코드에서 생기는 작은 비용
전역 비용 멀리 퍼져 있는 여러 코드가 함께 흔들리는 큰 비용

1. 귀납 도입: 같은 연결인데 왜 어떤 것은 괜찮고 어떤 것은 위험할까?

두 코드가 있다고 해보자.

static dynamic ReceiptTotal(dynamic order) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    return order.Subtotal + order.Tax; // 결과를 호출자에게 돌려줍니다.
}

이 코드는 order 안의 subtotal, tax를 알고 있다.
receipt_totalorder 구조에 의존한다.

그런데 이 연결이 꼭 나쁜가?

상황 A:

  • receipt_totalOrder가 같은 파일 안에 있다.
  • 둘은 같은 개발자가 같이 고친다.
  • 주문 총액 구조는 거의 바뀌지 않는다.

상황 B:

  • receipt_total은 결제 서비스에 있다.
  • Order는 주문 서비스에 있다.
  • 주문 필드는 매주 바뀐다.

연결 세기는 비슷해 보인다.
하지만 상황 B가 훨씬 위험하다.

왜냐하면 B는 세 조건이 동시에 나쁘기 때문이다.

차원 상황 A 상황 B
세기 중간 중간
거리 가까움 멀음
변동성 낮음 높음

결합 균형은 이 표를 보는 습관이다.


2. 이 장에서 딱 5가지만

  1. 결합 비용은 세기 하나로 정해지지 않는다.
  2. 세기와 변동성이 만나면 안정성 문제가 생긴다.
  3. 거리와 변동성이 만나면 변경 비용이 커진다.
  4. 세기와 거리가 만나면 모듈성 문제가 생긴다.
  5. 세 차원이 모두 나쁘면 유지보수 노력이 폭발한다.

3. 결합 세기와 변동성: 강하게 묶인 것이 자주 바뀌면 위험하다

먼저 세기와 변동성의 조합을 보자.

강하게 묶였다는 말은 한쪽을 바꾸면 다른 쪽도 자세히 알아야 한다는 뜻이다.
변동성이 높다는 말은 앞으로 자주 바뀐다는 뜻이다.

둘이 만나면 이런 일이 생긴다.

자주 바뀌는 부품을 자세히 알고 있는 코드가 많아진다.

예를 들어 할인 정책이 자주 바뀐다고 하자.

static dynamic Checkout(dynamic order) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    if (order.Customer.Grade == "VIP") // 조건이 참일 때만 아래 결정을 실행합니다.
    {
        order.Discount = order.Total * 0.1; // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
    }
    else if (order.Customer.Grade == "NEW") // 앞 조건이 아니면 이 조건을 확인합니다.
    {
        order.Discount = order.Total * 0.05; // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
}
}

이 코드가 한 군데만 있으면 아직 괜찮다.
그런데 같은 판단이 배송, 쿠폰, 영수증, 정산에 흩어져 있으면 어떻게 될까?

VIP 할인율이 바뀔 때마다 여러 곳을 찾아 고쳐야 한다.
정책이 자주 바뀌니, 강한 결합의 비용이 계속 반복된다.

그래서 안정성은 이런 질문으로 본다.

"강하게 알고 있는 대상이 앞으로 자주 바뀌는가?"


4. 거리와 변동성: 멀리 떨어진 것이 자주 바뀌면 추적 비용이 커진다

두 번째는 거리와 변동성이다.

코드가 멀리 떨어져 있다는 말은 다음 뜻을 포함한다.

  • 다른 파일에 있다.
  • 다른 모듈에 있다.
  • 다른 서비스에 있다.
  • 다른 팀이 관리한다.
  • 배포 시점이 다르다.

멀리 떨어진 코드는 변경을 맞추기 어렵다.
여기에 변동성이 높으면 비용이 더 커진다.

예를 들어 주문 서비스가 매주 응답 형태를 바꾼다고 하자.

{
  "orderId": "A-100",
  "total": 32000
}

다음 주에는 이렇게 바뀐다.

{
  "id": "A-100",
  "amount": 32000,
  "currency": "KRW"
}

같은 팀 안에서 바로 고치면 단순한 수정일 수 있다.
하지만 이 응답을 결제, 배송, 알림, 관리자 화면이 모두 쓰고 있다면 이야기가 달라진다.

변경은 코드 한 줄이 아니라 조율 일이 된다.

  • 누가 먼저 바꾸나?
  • 예전 응답도 잠시 유지해야 하나?
  • 배포 순서는 어떻게 되나?
  • 실패하면 어느 서비스가 깨지나?

거리와 변동성의 조합은 변경을 "수정"에서 "협상"으로 바꾼다.


5. 세기와 거리: 멀리 있는데 강하게 묶이면 모듈성이 무너진다

세 번째는 세기와 거리다.

멀리 떨어진 부품은 보통 독립적으로 이해하고 바꿀 수 있어야 한다.
그런데 멀리 떨어져 있으면서 내부 사정까지 강하게 알면 문제가 된다.

예를 들어 화면 코드가 데이터베이스 테이블 구조를 직접 안다고 해보자.

static dynamic RenderUser(dynamic row) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    return $"{row["first_name"]} {row["last_name"]} ({row["tier_code"]})"; // 결과를 호출자에게 돌려줍니다.
}

이 코드는 화면 코드인데 데이터베이스 컬럼 이름을 안다.
거리로 보면 화면과 DB는 꽤 멀다.
세기로 보면 구체적인 내부 구조를 알고 있으니 강하다.

이럴 때 모듈성은 약해진다.

왜냐하면 화면을 보려면 DB 구조를 알아야 하고, DB를 바꾸려면 화면을 찾아야 하기 때문이다.

모듈은 "나만 보면 된다"는 느낌을 줘야 한다.
강한 원거리 결합은 그 느낌을 빼앗는다.


6. 세 차원이 모두 나쁘면 유지보수 노력이 폭발한다

원서의 중요한 생각을 아주 단순하게 쓰면 이렇다.

유지보수 노력 ≈ 결합 세기 × 결합 거리 × 변동성

정확한 수학 공식이라기보다 사고 도구로 이해하면 된다.

셋 중 하나가 낮으면 전체 비용이 줄어들 수 있다.

세기 거리 변동성 해석
강함 가까움 낮음 괜찮을 수 있다
강함 가까움 높음 자주 같이 고치면 된다
약함 멀음 높음 계약만 안정적이면 버틸 수 있다
강함 멀음 높음 위험하다

위험한 조합은 마지막 줄이다.

강하게 묶였고, 멀리 떨어져 있으며, 자주 바뀐다.
이런 연결은 작은 변경도 여러 사람과 여러 파일을 흔든다.


7. 균형 공식은 외우는 것이 아니라 판단 순서다

원서에는 균형을 이렇게 표현한다.

BALANCE = (STRENGTH XOR DISTANCE) OR NOT VOLATILITY

처음 보면 낯설다.
입문자용으로 풀면 이렇게 말할 수 있다.

강한 연결은 가까워야 하고, 먼 연결은 약해야 한다.
다만 거의 안 바뀌는 것은 조금 덜 엄격해도 된다.

XOR는 "둘이 달라야 좋다"는 뜻으로 보면 된다.

세기 거리 균형 감각
강함 가까움 좋다
약함 멀음 좋다
강함 멀음 나쁘다
약함 가까움 꼭 나쁘진 않지만 이득이 작을 수 있다

여기에 변동성을 더한다.

변동성 판단
낮음 조금 어긋나도 큰 사고가 안 날 수 있다
높음 균형을 더 엄격하게 맞춰야 한다

그러므로 균형 공식은 계산기가 아니라 질문 목록이다.


8. 강한 결합은 항상 나쁜가?

아니다.

강한 결합이 필요한 경우도 있다.

예를 들어 한 클래스 안의 작은 private 메서드들은 서로를 자세히 알아도 된다.

public class Invoice // 송장을 계산하는 부품입니다. 계산 책임을 어디에 둘지 보여 줍니다.
{
    public dynamic Total() // 이 메서드의 입력과 출력이 공개 약속입니다.
    {
        return this.Subtotal() + this.Tax(); // 결과를 호출자에게 돌려줍니다.

    }
    public dynamic Subtotal() // 이 메서드의 입력과 출력이 공개 약속입니다.
    {
        return sum(line.Price for line in this.Lines); // 결과를 호출자에게 돌려줍니다.

    }
    public dynamic Tax() // 이 메서드의 입력과 출력이 공개 약속입니다.
    {
        return this.Subtotal() * 0.1; // 결과를 호출자에게 돌려줍니다.
}
}

여기서 _tax_subtotal을 부르는 것은 강한 내부 연결이다.
하지만 가까이 있다.
같이 바뀐다.
한 사람이 한 덩어리로 이해할 수 있다.

이런 결합을 억지로 약하게 만들면 오히려 코드가 이상해질 수 있다.

public class Invoice // 송장을 계산하는 부품입니다. 계산 책임을 어디에 둘지 보여 줍니다.
{
    public dynamic Total(dynamic subtotal_calculator, dynamic tax_calculator) // 이 메서드의 입력과 출력이 공개 약속입니다.
    {
        var subtotal = subtotal_calculator.Calculate(this.Lines); // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
        var tax = tax_calculator.Calculate(subtotal); // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
        return subtotal + tax; // 결과를 호출자에게 돌려줍니다.
}
}

확장성은 생긴 것처럼 보이지만, 실제로 변하지 않는 작은 계산이라면 과하다.

좋은 설계는 결합 제거가 아니다.
필요한 연결을 알맞은 곳에 두는 것이다.


9. 약한 결합도 항상 좋은 것은 아니다

약하게 연결하려고 모든 것을 이벤트나 문자열 계약으로 바꾸면 어떻게 될까?

publish("invoice.Total.Requested", {"invoice_id": invoice_id}); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.

이 방식은 멀리 떨어진 서비스 사이에서는 좋을 수 있다.
하지만 같은 파일 안의 작은 계산까지 이렇게 만들면 이해가 어려워진다.

왜냐하면 독자가 흐름을 따라가려면 다음을 찾아야 하기 때문이다.

  • 누가 이벤트를 받는가?
  • payload 구조는 어디에 적혀 있는가?
  • 실패하면 누가 책임지는가?
  • 순서는 보장되는가?

약한 결합은 세기를 낮추지만, 거리를 늘리고 추적 비용을 늘릴 수 있다.

그래서 "약하게 만들자"가 아니라 "거리와 변동성에 맞게 약하게 만들자"가 맞다.


10. 가까운 곳에 강한 결합을 모아두기

균형 잡힌 설계에서 자주 보이는 모습은 이것이다.

강한 지식은 가까운 곳에 모아둔다.

예를 들어 주문 총액 계산에 필요한 세부 규칙이 있다면, 그 규칙을 주문 도메인 안에 모아둔다.

public class Order // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
    public dynamic Total() // 이 메서드의 입력과 출력이 공개 약속입니다.
    {
        return this.Subtotal() - this.Discount() + this.ShippingFee(); // 결과를 호출자에게 돌려줍니다.
}
}

다른 모듈은 세부 규칙을 직접 알지 않고 결과만 묻는다.

static dynamic RenderCheckout(dynamic order) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    return $"결제 금액: {order.Total()}원"; // 결과를 호출자에게 돌려줍니다.
}

이 구조의 장점은 분명하다.

  • 총액 계산 규칙은 Order 가까이에 있다.
  • 화면은 계산 규칙을 자세히 모른다.
  • 규칙이 바뀌어도 멀리 있는 화면 코드는 덜 흔들린다.

이것이 "강한 연결은 가까이에"라는 원칙이다.


11. 먼 곳에는 약한 계약을 두기

반대로 멀리 떨어진 부품 사이에는 약한 계약이 필요하다.

예를 들어 주문 서비스와 알림 서비스가 있다고 하자.

나쁜 방향:

notification.Send(; // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
    order.Customer.Profile.RealName,; // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
    order.Customer.Profile.Phone.Number,; // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
    order.Payment.Card.CompanyName,; // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.

알림 서비스가 주문 내부 구조를 너무 많이 안다.

나은 방향:

notification.SendOrderPaid(; // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
    var recipientName = order.RecipientName(); // 받는 사람 이름만 공개 약속으로 가져옵니다.
    var phone = order.RecipientPhone(); // 전화번호도 공개 메서드로만 가져옵니다.
    var order_id = order.Id,; // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.

더 멀리 떨어진 서비스라면 아예 명확한 메시지 계약을 둔다.

{
  "event": "OrderPaid",
  "orderId": "A-100",
  "recipientName": "김초보",
  "phone": "010-0000-0000"
}

먼 연결은 내부를 알면 위험하다.
그래서 멀수록 계약은 단순하고 안정적이어야 한다.


12. 변동성이 높은 곳은 완충 장치를 둔다

자주 바뀌는 외부 정책, 외부 API, 화면 요구사항은 그대로 깊숙이 넣지 않는 것이 좋다.

예를 들어 배송비 정책이 자주 바뀐다면, 여러 코드가 직접 정책을 계산하게 두지 않는다.

static dynamic ShippingFee(dynamic order) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    if (order.Total >= 50000) // 조건이 참일 때만 아래 결정을 실행합니다.
    {
        return 0; // 결과를 호출자에게 돌려줍니다.
    }
    return 3000; // 결과를 호출자에게 돌려줍니다.
}

이 함수 자체가 문제는 아니다.
문제는 이 계산이 여러 곳에 흩어지는 것이다.

균형 잡힌 방향:

public class ShippingPolicy // 배송비 규칙입니다. 규칙이 자주 바뀌면 별도 부품으로 분리할 이유가 생깁니다.
{
    public dynamic FeeFor(dynamic order) // 이 메서드의 입력과 출력이 공개 약속입니다.
    {
        if (order.Total >= 50000) // 조건이 참일 때만 아래 결정을 실행합니다.
        {
            return 0; // 결과를 호출자에게 돌려줍니다.
        }
        return 3000; // 결과를 호출자에게 돌려줍니다.
}
}

그리고 다른 곳은 정책 객체에게 묻는다.

var fee = shipping_policy.FeeFor(order); // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.

변동성이 높은 지식은 한곳에 모아야 한다.
그래야 자주 바뀌어도 변경의 파도가 작아진다.


13. 불균형을 알아보는 질문

설계를 볼 때 다음 질문을 던져보자.

  1. 이 연결은 얼마나 강한가?
  2. 두 부품은 얼마나 멀리 떨어져 있는가?
  3. 이 지식은 앞으로 얼마나 자주 바뀔 것 같은가?
  4. 자주 바뀌는 지식이 멀리 퍼져 있지는 않은가?
  5. 멀리 있는 부품이 내부 구조를 너무 자세히 알지는 않는가?
  6. 약하게 만들려고 흐름을 너무 숨기지는 않았는가?

이 질문은 정답을 자동으로 주지 않는다.
하지만 위험한 연결을 발견하게 해준다.


14. 작은 예제: 쿠폰 정책

처음에는 이렇게 시작할 수 있다.

static dynamic ApplyCoupon(dynamic order, dynamic coupon) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
    if (coupon.Type == "PERCENT") // 조건이 참일 때만 아래 결정을 실행합니다.
    {
        return order.Total * coupon.Rate; // 결과를 호출자에게 돌려줍니다.
    }
    if (coupon.Type == "FIXED") // 조건이 참일 때만 아래 결정을 실행합니다.
    {
        return coupon.Amount; // 결과를 호출자에게 돌려줍니다.
    }
    return 0; // 결과를 호출자에게 돌려줍니다.
}

작은 프로젝트에서는 괜찮다.
함수와 쿠폰 구조가 가깝고, 자주 바뀌지 않는다면 충분하다.

그런데 시간이 지나 쿠폰 정책이 자주 바뀐다.

  • 신규 가입 쿠폰
  • 카테고리 쿠폰
  • 기간 한정 쿠폰
  • 중복 적용 금지
  • VIP 전용 쿠폰

그리고 이 로직이 결제, 장바구니, 관리자 미리보기, 정산에 흩어진다.

이제 균형이 깨졌다.

차원 상태
세기 쿠폰 내부 규칙을 자세히 안다
거리 여러 모듈에 퍼졌다
변동성 자주 바뀐다

이때는 정책을 모으고, 멀리 있는 코드는 결과만 묻게 해야 한다.

public class CouponPolicy // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
    public dynamic DiscountFor(dynamic order, dynamic coupon) // 이 메서드의 입력과 출력이 공개 약속입니다.
    {
        /* 구현 생략 */ // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
}
}

이 변경은 멋진 패턴을 쓰기 위해서가 아니다.
깨진 균형을 다시 맞추기 위해서다.


15. 균형은 점수보다 방향이다

원서에는 균형을 숫자로 표현하는 식도 나온다.

BALANCE = max(|STRENGTH - DISTANCE|, 10 - VOLATILITY) + 1

입문 단계에서는 숫자 자체보다 뜻을 잡는 것이 중요하다.

  • 세기와 거리가 잘 어울리는가?
  • 변동성이 높을수록 더 엄격하게 봐야 하지 않는가?
  • 지금 비용이 아니라 앞으로의 변경 비용까지 봐야 하지 않는가?

숫자는 토론을 돕는 도구다.
팀이 같은 연결을 보며 같은 질문을 던지게 만든다.


16. 한 줄 묶음표

상황 균형 감각
강하고 가까움 자연스럽다
약하고 멈 자연스럽다
강하고 멈 위험하다
약하고 가까움 과한 분리일 수 있다
자주 바뀌고 강함 변경이 반복된다
자주 바뀌고 멂 조율 비용이 커진다
자주 바뀌고 강하고 멂 가장 조심해야 한다

17. 정리

10장은 결합을 보는 관점을 완성한다.

이제 우리는 결합을 한 단어로 판단하지 않는다.

  • 강한가?
  • 먼가?
  • 자주 바뀌는가?

이 세 질문을 함께 던진다.

강한 연결은 가까이에 둔다.
먼 연결은 약하게 만든다.
자주 바뀌는 지식은 흩어지지 않게 한다.
거의 바뀌지 않는 것은 조금 더 단순하게 둔다.

이것이 결합 균형이다.


더 해보기

  1. 최근에 고친 코드 하나를 떠올려보자. 그 변경은 어디까지 퍼졌는가?
  2. 그 연결의 세기, 거리, 변동성을 각각 낮음/중간/높음으로 표시해보자.
  3. 가장 위험한 연결 하나를 골라 "가까이 모을지, 약하게 만들지, 변동성을 격리할지" 적어보자.

연습문제

  1. 설명. 세 힘을 함께 맞추기의 핵심을 처음 듣는 사람에게 한 문장으로 설명하라.
  2. 구분. 두 개념(강도, 거리)을 실제 예시 하나로 구분하라.
  3. 적용. 내 프로젝트나 학습 노트에서 이 장의 개념을 적용해 작게 개선할 지점을 하나 고르라.

부록 A. 쉬운 용어 사전

한글 용어 원문 영문명 아주 쉬운 뜻 이 장에서 나온 위치
강도 Integration Strength 부품들이 얼마나 많이 알고 얼마나 세게 묶여 있는지. 부록 B와 본문 예시
거리 Distance 함께 바꾸기 위해 넘어야 하는 코드, 팀, 배포상의 간격. 부록 B와 본문 예시
균형 Balance 강도, 거리, 변동성을 함께 보고 비용을 맞추는 설계 판단. 부록 B와 본문 예시
최적화 Optimization 특정 기준 하나를 더 좋게 만들려고 밀어붙이는 일. 부록 B와 본문 예시

부록 B. 헷갈리는 개념 비교표

A B 구분 포인트
강도 거리 강도는 얼마나 꽉 묶였는가, 거리는 함께 바꾸기 얼마나 어려운가다.
균형 최적화 균형은 세 힘의 비용을 맞추는 일이고, 최적화는 한 축만 지나치게 밀 수 있다.

부록 C. 더 읽을 자료

  • 이 장의 더 해보기 섹션 — 이미 모아 둔 공식 문서나 실습 링크가 있으면 여기서 먼저 확인한다.
  • 같은 책의 0장 한눈에 보기 — 용어가 막히면 0장의 용어집과 개념 척추로 돌아간다.
  • 원본 딥다이브판 같은 장 — 입문판을 읽고 큰 흐름이 잡힌 뒤 세부 논리를 더 깊게 확인한다.
  • 이 장의 flashcards.json — 읽은 직후 질문만 보고 답을 떠올리는 회상 연습에 쓴다.

부록 D. 연습문제 풀이

  1. 설명 예시. 세 힘을 함께 맞추기는 변경이 어디로 번지는지 보고, 필요한 연결과 줄여야 할 연결을 구분하게 해 주는 장이다. 중요한 것은 용어를 외우는 것이 아니라, 이 개념이 어떤 입력·부품·결정에 영향을 주는지 말로 풀어 보는 것이다.
  2. 구분 예시. 두 개념(강도, 거리)의 차이는 이렇게 잡으면 된다. 강도는 얼마나 꽉 묶였는가, 거리는 함께 바꾸기 얼마나 어려운가다. 실제 사례를 볼 때는 목적, 입력, 실패했을 때의 증상을 따로 적어 보면 헷갈리지 않는다.
  3. 적용 예시. 가장 작은 개선부터 고른다. 예를 들어 이름을 더 분명히 하거나, 평가 기준을 한 줄 추가하거나, 직접 알 필요 없는 내부 정보를 감추는 식으로 시작한다. 한 번에 크게 갈아엎는 것보다 작은 변경 하나를 확인하며 진행하는 쪽이 입문 단계에 맞다.
난이도
에피소드
질문
카드를 로딩 중...
답변

클릭하거나 Space를 눌러 뒤집기

0 / 0
학습 진도 0%
이동   Space 뒤집기   R 셔플